
#include "arch/loongarch.h"
#include "config/type.h"
#include "config/sys_param.h"
#include "lib/stdout.h"
#include "lib/string.h"
#include "mm/phylayout.h"
#include "mm/vm.h"
#include "sched/proc.h"

int nextpid = 1;
struct cpu cpus[MAXCPU];
struct proc proc[MAXPROC];
struct proc *initproc;
struct spinlock pid_lock;

extern void forkret(void);
static void freeproc(struct proc *p);

// 此处 wait_lock 确保 wait_k() 的唤醒不会丢失，当使用 p->parent 时，用这把锁遵守内存模型。
// 必须在任何 p->lock 之前就上锁。
struct spinlock wait_lock;

// 为每个进程的内核栈分配一个页面。把它映射到内存的高位，然后是一个无效的保护页。
void proc_mapstacks(pagetable_t kpgtbl) {
	int i;
	struct proc *p;
	for (i = 0; i < MAXPROC; i++) {
		p = &proc[i];
		// for (p = proc; p < &proc[MAXPROC]; p++) 
		// 原循环有问题，p++ 根本不会改变 p
		char *pa = kalloc();
		// pr_warn(proc_mapstacks, "process[%d]: proc *p = %p, pa = %p", i, p, (uint64)pa);
		if (pa == 0)
			panic(proc_mapstacks, "process[%d]: kalloc() failed", i);
		uint64 va = KSTACK((int)(p - proc));
		// TODO: 这里也需要检查页表项设置和用到的所有宏
		if (mappages(kpgtbl, va, PGSIZE, (uint64)pa, PTE_NX | PTE_P | PTE_W | PTE_MAT | PTE_D) != 0)
			panic(proc_mapstacks, "mappages() failed, va = %p, pa = %p", va, pa);
	}
}

// 在 boot 时初始化 proc 表
void procinit(void) {
	int i;
	struct proc *p;
	initlock(&pid_lock, "nextpid");
	initlock(&wait_lock, "wait_lock");
	for (i = 0; i < MAXPROC; i++) {
		// for (p = proc; p < &proc[MAXPROC]; p++)
		// 原循环有问题 * 2
		p = &proc[i];
		initlock(&p->lock, "proc");
		p->state = UNUSED;
		p->kstack = KSTACK((int)(p - proc));
	}
}

// 取得线程指针。
// 为了防止 CPU 之间的数据竞争，该函数必须在关中断时才能调用
int cpuid() {
	int id = r_tp();
	return id;
}

// 返回这个 CPU 的 cpu 结构体。
// 该函数必须在关中断时才能调用
struct cpu *mycpu(void) {
	int id = cpuid();
	struct cpu *c = &cpus[id];
	return c;
}

// 返回当前的 proc 结构体，如果没有则返回 0
struct proc *myproc(void) {
	// push_off();
	struct cpu *c = mycpu();
	struct proc *p = c->proc;
	// pop_off();
	return p;
}

// 分配下一个进程编号
int allocpid() {
	int pid;

	acquire(&pid_lock);
	pid = nextpid;
	nextpid = nextpid + 1;
	release(&pid_lock);

	return pid;
}

// 在进程表中寻找一个未使用的进程。
// 如果找到，在页表中分配一个槽（即结构体 struct proc），并初始化进程的状态，
// 为其内核线程的运行做准备，并在 p->lock 保持的情况下返回。
// 如果没有空闲的proc，或者内存分配失败，则返回0。
static struct proc *allocproc(void) {
	struct proc *p;

	for (p = proc; p < &proc[MAXPROC]; p++) {
		acquire(&p->lock);
		if (p->state == UNUSED) {
			goto found;
		} else {
			release(&p->lock);
		}
	}
	return 0;

found:
	p->pid = allocpid();
	p->state = USED;

	// 分配 trapframe 数据页
	if ((p->trapframe = (struct trapframe *)kalloc()) == 0) {
		freeproc(p);
		release(&p->lock);
		return 0;
	}

	// 一个空的用户页表
	p->pagetable = proc_pagetable(p);
	if (p->pagetable == 0) {
		freeproc(p);
		release(&p->lock);
		return 0;
	}

	// 设置新的上下文
	memset(&p->context, 0, sizeof(p->context));
	// allocproc 通过把 initproc 的 p->context->eip 设置为 forkret 
	// 使 ret 开始执行 forkret 的代码
	p->context.ra = (uint64)forkret;
	// 然后返回到用户空间
	p->context.sp = p->kstack + PGSIZE;

	return p;
}

// 释放一个 proc 结构体和挂在上面的数据，包括用户页。
// 调用本函数时必须先上锁 p->lock
static void freeproc(struct proc *p) {
	if (p->trapframe)
		kfree((void *)p->trapframe);
	p->trapframe = 0;
	if (p->pagetable)
		proc_freepagetable(p->pagetable, p->sz);
	p->pagetable = 0;
	p->sz = 0;
	p->pid = 0;
	p->parent = 0;
	p->name[0] = 0;
	p->chan = 0;
	p->killed = 0;
	p->xstate = 0;
	p->state = UNUSED;
}

// 为给定的进程创建一个用户页表，没有用户内存，但有 KSTACK 页
pagetable_t proc_pagetable(struct proc *p) {
	pagetable_t pagetable;

	// 一个空页表
	pagetable = uvmcreate();
	if (pagetable == 0)
		return 0;

	// 将 trapframe 映射到 KSTACK 的第 2 页下，用于 uservec.S
	if (mappages(pagetable, TRAPFRAME, PGSIZE, (uint64)(p->trapframe),
							 PTE_NX | PTE_P | PTE_W | PTE_MAT | PTE_D) < 0) {
		uvmfree(pagetable, 0);
		return 0;
	}

	return pagetable;
}

// 释放进程的页表，并释放它所指的物理内存。
void proc_freepagetable(pagetable_t pagetable, uint64 sz) {
	uvmunmap(pagetable, TRAPFRAME, 1, 0);
	uvmfree(pagetable, sz);
}

// TODO: 换成能跳转调用 exec("/init") 进用户态的程序
uchar initcode[] = {
	0x04, 0x00, 0x00, 0x1c, 0x84, 0x00, 0xc1, 0x28, 0x05, 0x00, 0x00, 0x1c,
	0xa5, 0x00, 0xc1, 0x28, 0x0b, 0x1c, 0x80, 0x03, 0x00, 0x00, 0x2b, 0x00,
	0x0b, 0x08, 0x80, 0x03, 0x00, 0x00, 0x2b, 0x00, 0xff, 0xfb, 0xff, 0x57,
	0x2f, 0x69, 0x6e, 0x69, 0x74, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

// 初始化第一个用户进程
void userinit(void) {
	struct proc *p;

	p = allocproc();
	initproc = p;

	// 分配一个用户页，将 initcode 的指令和数据复制到其中。
	uvminit(p->pagetable, initcode, sizeof(initcode));
	p->sz = PGSIZE;

	// 为第一次从内核到用户的返回做准备
	p->trapframe->era = 0;		// user program counter
	p->trapframe->sp = PGSIZE;	// user stack pointer

	safestrcpy(p->name, "initcode", sizeof(p->name));
	// p->cwd = namei("/");

	p->state = RUNNABLE;

	release(&p->lock);
}

// 将用户内存增加或缩小 n 个字节。
// 成功时返回 0，失败时返回 1。
int growproc(int n) {
	uint sz;
	struct proc *p = myproc();

	sz = p->sz;
	if (n > 0) {
		if (sz + n >= MAXVA - PGSIZE)
			return -1; // trampoline
		if ((sz = uvmalloc(p->pagetable, sz, sz + n)) == 0) {
			return -1;
		}
	} else if (n < 0) {
		sz = uvmdealloc(p->pagetable, sz, sz + n);
	}
	p->sz = sz;
	return 0;
}

// 创建一个新进程，复制它的父进程。
// 设置子内核堆栈，以便像 fork_k() 系统调用一样返回。
int fork_k(void) {
	int i, pid;
	struct proc *np;
	struct proc *p = myproc();

	// 分配进程
	if ((np = allocproc()) == 0) {
		return -1;
	}

	// 从父进程拷贝用户内存
	if (uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) {
		freeproc(np);
		release(&np->lock);
		return -1;
	}
	np->sz = p->sz;

	// 拷贝用户寄存器
	*(np->trapframe) = *(p->trapframe);

	// 导致 fork 在子进程中返回0。
	np->trapframe->a0 = 0;

	// 在打开的文件描述符上增加参考计数。
	// TODO: 目前没有文件系统
	// for (i = 0; i < NOFILE; i++)
	// 	if (p->ofile[i])
	// 		np->ofile[i] = filedup(p->ofile[i]);
	// np->cwd = idup(p->cwd);

	safestrcpy(np->name, p->name, sizeof(p->name));

	pid = np->pid;

	release(&np->lock);

	acquire(&wait_lock);
	np->parent = p;
	release(&wait_lock);

	acquire(&np->lock);
	np->state = RUNNABLE;
	release(&np->lock);

	return pid;
}

// 将 p 的被抛弃的子进程传递给 init。
// 要求调用者必须持有 wait_lock。
void reparent(struct proc *p) {
	struct proc *pp;

	for (pp = proc; pp < &proc[MAXPROC]; pp++) {
		if (pp->parent == p) {
			pp->parent = initproc;
			wakeup(initproc);
		}
	}
}

// 退出当前进程，不返回。
// 已退出的进程仍处于 ZOMBIE 状态，直到它的父进程调用 wait_k()。
void exit_k(int status) {
	struct proc *p = myproc();

	if (p == initproc) {
		pr_error(exit, "p == initproc, Can't exit from the initial proc.");
		return;
	}

	// TODO: 缺少文件系统
	// Close all open files.
	// for (int fd = 0; fd < NOFILE; fd++) {
	// 	if (p->ofile[fd]) {
	// 		struct file *f = p->ofile[fd];
	// 		fileclose(f);
	// 		p->ofile[fd] = 0;
	// 	}
	// }
	// begin_op();
	// iput(p->cwd);
	// end_op();
	// p->cwd = 0;

	acquire(&wait_lock);

	// 该进程的子进程都挂到 init 进程下
	reparent(p);

	// 唤醒该进程的父进程
	wakeup(p->parent);

	acquire(&p->lock);

	p->xstate = status;
	p->state = ZOMBIE;

	release(&wait_lock);

	// 跳转到 sched()
	sched();
	panic(exit, "zombie process exit jump to sched() failed");
}

// 等待一个子进程退出并返回其pid。
// 如果这个进程没有子进程，则返回-1。
int wait_k(uint64 addr) {
	struct proc *np;
	int havekids, pid;
	struct proc *p = myproc();

	acquire(&wait_lock);

	for (;;) {
		// 扫描全表找退出的子进程。
		havekids = 0;
		for (np = proc; np < &proc[MAXPROC]; np++) {
			if (np->parent == p) {
				// 确保子进程没有仍在 exit_k() 或 swtch() 中。
				acquire(&np->lock);

				havekids = 1;
				if (np->state == ZOMBIE) {
					// 找到时
					pid = np->pid;
					if (addr != 0 && copyout(p->pagetable, addr, (char *)&np->xstate,
																	 sizeof(np->xstate)) < 0) {
						release(&np->lock);
						release(&wait_lock);
						return -1;
					}
					freeproc(np);
					release(&np->lock);
					release(&wait_lock);
					return pid;
				}
				release(&np->lock);
			}
		}

		// 如果没有子进程，就没有必要再等了。
		if (!havekids || p->killed) {
			release(&wait_lock);
			return -1;
		}

		// 等待子进程退出。
		sleep_k(p, &wait_lock); // DOC: wait-sleep
	}
}

// scheduler() 是CPU的进程调度器，每个CPU在设置好自己后调用scheduler()。
// 调度器从不结束，它循环运行，进行：
// - 选择一个要运行的进程。
// - 启动运行该进程的swtch。
// - 最终该进程将控制权通过swtch回到调度器。
void scheduler(void) {
	struct proc *p;
	struct cpu *c = mycpu();

	c->proc = 0;
	for (;;) {
		// 关中断防死锁
		intr_on();

		for (p = proc; p < &proc[MAXPROC]; p++) {
			acquire(&p->lock);
			if (p->state == RUNNABLE) {
				// 切换到选定的进程。进程的任务是释放它的锁，然后在跳回我们面前重新获得它。
				p->state = RUNNING;
				c->proc = p;
				swtch(&c->context, &p->context);

				// 进程现在已经完成了运行。在回来之前，它应该已经改变了它的p->state。
				c->proc = 0;
			}
			release(&p->lock);
		}
	}
}

// 本函数的功能是调用 swtch 切换到 cpu->scheduler，要求必须持有 p->lock 锁并已经改变了 proc->state。
// 保存和恢复 intena 是因为 intena 是内核线程的属性，而不是这个 CPU。
// 它应该是 proc->intena 和 proc->noff，但这在少数持有锁但没有进程的地方会被破坏。
void sched(void) {
	int intena;
	struct proc *p = myproc();

	if (!holding(&p->lock))
		panic(sched, "holding(&p->lock) == 0");
	if (mycpu()->noff != 1)
		panic(sched, "mycpu()->noff != 1");
	if (p->state == RUNNING)
		panic(sched, "p->state == RUNNING");
	if (intr_get())
		panic(sched, "interrupts enabled");

	intena = mycpu()->intena;
	// swtch 是汇编函数，只简单保存和恢复寄存器
	swtch(&p->context, &mycpu()->context);
	// swtch 返回时不会返回到 sched 中，而是返回到 scheduler，栈指针指向当前 CPU 的调度器的栈
	mycpu()->intena = intena;
}

// 放弃CPU，进行一轮调度。
void yield(void) {
	struct proc *p = myproc();
	acquire(&p->lock);
	p->state = RUNNABLE;
	sched();
	release(&p->lock);
}

// fork 的子进程第一次被 scheduler() 调度的时候，会切换到 forkret。
void forkret(void) {
	static int first = 1;

	// 仍然持有scheduler的p->lock。
	release(&myproc()->lock);

	if (first) {
		// TODO: 没有可用的文件系统 
		first = 0;
		// 文件系统的初始化必须在普通进程的上下文中运行（因为它调用睡眠），因此不能从main()运行。
		// fsinit(ROOTDEV);
	}
	// TODO: 这个函数没实现
	// usertrapret();
}

// sleep 将当前进程转化为 SLEEPING 状态并调用 sched 以释放 CPU
void sleep_k(void *chan, struct spinlock *lk) {
	struct proc *p = myproc();

	// 必须获得p->lock，以便改变p->state，然后调用sched。
	// 一旦我们持有p->lock，我们就可以保证不会错过任何唤醒（唤醒会锁定p->lock），所以释放lk是可以的。
	acquire(&p->lock);	// DOC: sleeplock1
	release(lk);

	// 睡眠。
	p->chan = chan;
	p->state = SLEEPING;

	sched();

	// 整理。
	p->chan = 0;

	// 重新获得原来的锁。
	release(&p->lock);
	acquire(lk);
}

// wakeup 寻找睡眠状态的进程并把它标记为 RUNNABLE。
// 必须在没有 p->lock 的时候被调用。
void wakeup(void *chan) {
	struct proc *p;

	for (p = proc; p < &proc[MAXPROC]; p++) {
		if (p != myproc()) {
			acquire(&p->lock);
			if (p->state == SLEEPING && p->chan == chan) {
				p->state = RUNNABLE;
			}
			release(&p->lock);
		}
	}
}

// 终止给定 pid 的某进程。
// 该进程直到试图返回到用户空间之前不会退出，参考 trap.c 中的usertrap()
int kill_k(int pid) {
	struct proc *p;

	for (p = proc; p < &proc[MAXPROC]; p++) {
		acquire(&p->lock);
		if (p->pid == pid) {
			p->killed = 1;
			if (p->state == SLEEPING) {
				// 从 sleep_k() 唤醒进程。
				p->state = RUNNABLE;
			}
			release(&p->lock);
			return 0;
		}
		release(&p->lock);
	}
	return -1;
}

// 拷贝到用户地址，或者内核地址，取决于usr_dst。
// 成功时返回0，错误时返回1。
int either_copyout(int user_dst, uint64 dst, void *src, uint64 len) {
	struct proc *p = myproc();
	if (user_dst) {
		return copyout(p->pagetable, dst, src, len);
	} else {
		memmove((char *)dst, src, len);
		return 0;
	}
} // TODO: xv6-loongarch 未完全实现

// 从用户地址或内核地址复制，取决于usr_src。
// 成功时返回0，错误时返回1。
int either_copyin(void *dst, int user_src, uint64 src, uint64 len) {
	struct proc *p = myproc();
	if (user_src) {
		return copyin(p->pagetable, dst, src, len);
	} else {
		memmove(dst, (char *)src, len);
		return 0;
	}
} // TODO: xv6-loongarch 未完全实现

// 打印一个进程列表到控制台显示，用于调试。
// 不使用锁，以避免进一步卡住机器。
void procdump(void) {
	static char *states[] = {[UNUSED]	"unused   ",
							 [SLEEPING] "sleep    ",
							 [RUNNABLE] "runnable ",
							 [RUNNING]	"running  ",
							 [ZOMBIE]	"zombie   "};
	struct proc *p;
	char *state;
	int i;

	for (i=0; i<MAXPROC; i++) {
		p = &proc[i];
		if (p->state == UNUSED) {
			state = states[p->state];
			printk("process[%d]\t%s\n", i, state);
		} else {
			if (p->state >= 0 && p->state < NELEM(states) && states[p->state])
				state = states[p->state];
			else
				state = "???";
			printk("process[%d]\t%s\t%d\t%s\n", i, state, p->pid, p->name);
		}
	}
}